
#include <BFont>
#include <BPainter>
#include <BEvent>
#include <BMouseEvent>
#include <BKeyEvent>
#include <BCharEvent>
#include <BStyle>
#include <BSystem>

#include <BComboBox>
#include "member_BComboBox.h"
#include "member_BComboItem.h"

using namespace BWE;

#define member					(*(member_BComboBox*)_ptr)
#define member_allocate()		_ptr = new member_BComboBox(this)
#define member_release()		delete (member_BComboBox*)_ptr

BComboBox::BComboBox(BWidget* parent) : BWidget(parent)
{
	member_allocate();
}
BComboBox::~BComboBox()
{
	member_release();
}

void BComboBox::setAlign(Align align)
{
	if (member.align != align)
	{
		member.align = align;
		member.dirty = true;
		this->fresh();
	}
}
Align BComboBox::align() const
{
	return member.align;
}

void BComboBox::setForm(Form form)
{
	if (member.form != form)
	{
		member.form = form;
		member.dirty = true;
		this->fresh();
	}
}
Form BComboBox::form() const
{
	return member.form;
}

void BComboBox::setSpacing(int spacing)
{
	if (member.spacing != spacing)
	{
		member.spacing = spacing;
		member.dirty = true;
		this->fresh();
	}
}
int BComboBox::spacing() const
{
	return member.spacing;
}

void BComboBox::setReadOnly(bool readOnly)
{
	if (member.readOnly != readOnly)
	{
		member.readOnly = readOnly;
		if (readOnly)
			this->setCursor(Cursor_None);
		else
			this->setCursor(Cursor_Ibeam);
		for (int i = 0; i < member.items.size(); i++)
		{
			BComboItem* item = member.items[i];
			item_member(item)->dirty = true;
		}
		member.dirty = true;
		this->fresh();
	}
}
bool BComboBox::readOnly() const
{
	return member.readOnly;
}

void BComboBox::setArrowShown(bool arrowShown)
{
	if (!member.arrow && arrowShown)
	{
		this->addChild(member.board->arrow);
		member.dirty = true;
		return;
	}
	if (member.arrow && !arrowShown)
	{
		this->removeChild(member.arrow);
		member.dirty = true;
		return;
	}
}
bool BComboBox::arrowShown() const
{
	return member.arrow;
}

void BComboBox::setText(const BString& text)
{
	if (member.readOnly)
	{
		BComboItem* item = itemOfText(text);
		this->setCurrentItem(item);
	}
	else
	{
		this->setCurrentItem(0);
		member.text = text;
		member.text.replace('\n', ' ');
		member.reset(member.text);
		member.dirty = true;
		this->fresh();
	}
}
const BString& BComboBox::text() const
{
	if (member.current)
		return member.current->text();
	return member.text;
}

void BComboBox::setValue(const BValue& value)
{
	if (!member.readOnly && member.current == 0)
	{
		if (member.value != value)
		{
			member.value = value;
			emit(Signal_ValueChanged, member.value);
		}
		return;
	}
	if (member.value != value)
	{
		for (int i = 0; i < member.items.size(); i++)
		{
			BComboItem* item = member.items[i];
			if (item->value() == value)
			{
				setCurrentItem(item);
			}
		}
	}
}
const BValue& BComboBox::value() const
{
	return member.value;
}

bool BComboBox::addItem(BComboItem* item)
{
	if (!item)
		return false;
	if (item_member(item)->combo == this)
		return true;
	if (!member.items.append(item))
		return false;
	if (item_member(item)->combo)
	{
		item_member(item)->combo->removeItem(item);
	}
	item_member(item)->combo = this;
	member.board->dirty = true;
	member.board->fresh();
	member.dirty = true;
	this->fresh();
	return true;
}
BComboItem* BComboBox::addItem(const BString& text, const BValue& value)
{
	BComboItem* item = new BComboItem(text, value);
	addItem(item);
	return item;
}
BComboItem* BComboBox::addItem(Graph graph, const BString& text, const BValue& value)
{
	BComboItem* item = new BComboItem(graph, text, value);
	addItem(item);
	return item;
}
BComboItem* BComboBox::addItem(BIcon* icon, const BString& text, const BValue& value)
{
	BComboItem* item = new BComboItem(icon, text, value);
	addItem(item);
	return item;
}
BComboItem* BComboBox::addItem(BImage* image, const BString& text, const BValue& value)
{
	BComboItem* item = new BComboItem(image, text, value);
	addItem(item);
	return item;
}

bool BComboBox::insertItem(int pos, BComboItem* item)
{
	if (!item)
		return false;
	if (item_member(item)->combo == this)
		return true;
	if (!member.items.insert(pos, item))
		return false;
	if (item_member(item)->combo)
	{
		item_member(item)->combo->removeItem(item);
	}
	item_member(item)->combo = this;
	member.board->dirty = true;
	member.board->fresh();
	return true;
}
BComboItem* BComboBox::insertItem(int pos, const BString& text, const BValue& value)
{
	BComboItem* item = new BComboItem(text, value);
	insertItem(pos, item);
	return item;
}
BComboItem* BComboBox::insertItem(int pos, Graph graph, const BString& text, const BValue& value)
{
	BComboItem* item = new BComboItem(graph, text, value);
	insertItem(pos, item);
	return item;
}
BComboItem* BComboBox::insertItem(int pos, BIcon* icon, const BString& text, const BValue& value)
{
	BComboItem* item = new BComboItem(icon, text, value);
	insertItem(pos, item);
	return item;
}
BComboItem* BComboBox::insertItem(int pos, BImage* image, const BString& text, const BValue& value)
{
	BComboItem* item = new BComboItem(image, text, value);
	insertItem(pos, item);
	return item;
}

bool BComboBox::removeItem(const BComboItem* item)
{
	if (!item || item_member(item)->combo != this)
		return false;
	if (member.current == item)
	{
		setCurrentItem(0);
	}
	item_member(item)->combo = 0;
	member.items.remove(item);
	member.board->dirty = true;
	member.board->fresh();
	return true;
}
bool BComboBox::removeItem(const BString& text)
{
	for(int i = 0; i < member.items.size(); i++)
	{
		BComboItem* item = member.items[i];
		if(item->text() == text)
		{
			return removeItem(item);
		}
	}
	return false;
}
bool BComboBox::removeItem(int index)
{
	BComboItem* item = member.items(index);
	return removeItem(item);
}

void BComboBox::clearItems()
{
	if (member.items.size())
	{
		member.items.clear();
		member.board->dirty = true;
		member.board->fresh();
		member.dirty = true;
		member.text.clear();
		if (member.current)
			setCurrentItem(0);
		this->fresh();
	}
}

bool BComboBox::containItem(const BValue& value) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BComboItem* item = member.items[i];
		if (item->value() == value)
			return true;
	}
	return false;
}
bool BComboBox::containItem(const BString& text) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BComboItem* item = member.items[i];
		if (item->text() == text)
			return true;
	}
	return false;
}

int BComboBox::itemCount() const
{
	return member.items.size();
}
BComboItem* BComboBox::item(int index)
{
	return member.items(index);
}
const BComboItem* BComboBox::item(int index) const
{
	return member.items(index);
}

BComboItem* BComboBox::itemOfText(const BString& text)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BComboItem* item = member.items[i];
		if (item->text() == text)
			return item;
	}
	return 0;
}
const BComboItem* BComboBox::itemOfText(const BString& text) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		const BComboItem* item = member.items[i];
		if (item->text() == text)
			return item;
	}
	return 0;
}

BComboItem* BComboBox::itemOfValue(const BValue& value)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BComboItem* item = member.items[i];
		if (item->value() == value)
			return item;
	}
	return 0;
}
const BComboItem* BComboBox::itemOfValue(const BValue& value) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		const BComboItem* item = member.items[i];
		if (item->value() == value)
			return item;
	}
	return 0;
}

bool BComboBox::moveItem(const BComboItem* item, int pos)
{
	int index = member.items.find(item);
	if (member.items.move(index, pos))
	{
		int min = bMin(index, pos);
		int max = bMax(index, pos);
		member.board->fresh();
		return true;
	}
	return false;
}
bool BComboBox::moveItem(const BString& text, int pos)
{
	BComboItem* item = itemOfText(text);
	return moveItem(item, pos);
}
bool BComboBox::moveItem(const BValue& value, int pos)
{
	BComboItem* item = itemOfValue(value);
	return moveItem(item, pos);
}

bool BComboBox::setCurrentText(const BString& text)
{
	for(int i = 0; i < member.items.size(); i++)
	{
		BComboItem* item = member.items[i];
		if(item->text() == text)
		{
			return setCurrentItem(item);
		}
	}
	return setCurrentItem(0);
}
bool BComboBox::setCurrentItem(BComboItem* item)
{
	if (item && item_member(item)->combo != this)
		return false;
	if (member.current != item)
	{
		member.current = item;
		member.value.clear();
		member.clear();
		if (member.current)
		{
			member.value = item->value();
			if (item_member(item)->t_text.size())
				member.reset(item_member(item)->t_text);
			else
				member.reset(item_member(item)->text);
		}
		bool checked = member.arrow->checked();
		member.board->fresh();
		member.dirty = true;
		this->fresh();
		emit(Signal_TextChanged, this->text());
		emit(Signal_ValueChanged, member.value);
		emit(Signal_CurrentChanged, member.current);
		return true;
	}
	return false;
}
const BComboItem* BComboBox::currentItem() const
{
	return member.current;
}
BComboItem* BComboBox::currentItem()
{
	return member.current;
}

bool BComboBox::setCurrentIndex(int index)
{
	BComboItem* item = member.items(index);
	return setCurrentItem(item);
}
int BComboBox::currentIndex() const
{
	if (member.current)
		return member.current->index();
	return -1;
}

void BComboBox::setIconSize(const BSize& iconSize)
{
	if (member.iconSize != iconSize)
	{
		member.iconSize = iconSize;
	}
}
const BSize& BComboBox::iconSize() const
{
	return member.iconSize;
}

BSize BComboBox::sizeHint() const
{
	BSize hsize = BWidget::sizeHint();
	if (const BFont* font = this->font())
	{
		int cw = font->codeWidth('0');
		hsize.width() += font->size() * 8;
		if (hsize < font->size() + 3)
			hsize.height() = font->size() + 3;
	}
	if (member.dirty)
	{
		member.freshRects();
		member.dirty = false;
	}
	if (hsize.height() < member.contentSize.height())
		hsize.height() = member.contentSize.height();
	return hsize;
}

bool BComboBox::query(const BString& text, BValue& value) const
{
	if (text == "board")
	{
		value = member.board;
		return true;
	}
	if (text == "arrow")
	{
		value = member.board->arrow;
		return true;
	}
	if (text == "icon-rect")
	{
		value = member.iconRect;
		return true;
	}
	if (text == "text-rect")
	{
		value = member.rect;
		return true;
	}
	return BWidget::query(text, value);
}

void BComboBox::updateEvent(const BEvent& event)
{
	if (this->focused() && !member.readOnly)
	{
		bool inputShown = true;
		if (member.blinkTime > 0)
		{
			clock_t now = clock();
			clock_t time = now - member.blinkTick;
			inputShown = time <= member.blinkTime / 2;
			if (time > member.blinkTime)
				member.blinkTick = now;
		}
		if (member.inputShown != inputShown)
		{
			member.inputShown = inputShown;
			this->fresh();
		}
	}
}
void BComboBox::resizeEvent(const BEvent& event)
{
	member.dirty = true;
	BWidget::resizeEvent(event);
}

void BComboBox::styleEvent(const BEvent& event)
{
	BWidget::styleEvent(event);
	if (const BStyle* style = this->realStyle())
	{
		const BValue& formValue = style->value(Value_Form);
		if (formValue.valid())
			member.form = formValue;

		if (const BFont* font = style->font())
		{
			member.setFont(font);
		}
	}
	for (int i = 0; i < member.items.size(); i++)
	{
		BComboItem* item = member.items[i];
		item_member(item)->dirty = true;
	}
	member.dirty = true;
	this->fresh();
}
void BComboBox::freshEvent(const BEvent& event)
{
	if (member.dirty)
	{
		member.freshRects();
		member.dirty = false;
	}
	BWidget::freshEvent(event);
}
void BComboBox::paintEvent(const BEvent& event)
{
	BPainter painter(this);

	State state = member.state;
	StyleRole textColorRole = Color_Text;
	if (!this->enabled())
	{
		state = State_Disable;
		textColorRole = Color_Text_Disable;
	}
	painter.drawBackground(state);
	painter.drawBorder(state);
	painter.drawPerch(state);
	painter.drawMargin(state);

	if (this->focused() && member.inputCount > 0)
	{
		const BRect& crect = this->centerRect();
		int inputCount = member.inputCount;
		painter.setColor(Color_Background_Checked);
		for (int i = 0; i < member.inputCount; i++)
		{
			int index = member.inputIndex + i;
			const CodeCell& cell = member.cells[index];
			BRect rect = cell.rect + member.rect.pos();
			if (rect.right() < crect.left())
				continue;
			if (rect.left() > crect.right())
				break;
			painter.fillRect(rect);
		}
	}

	const BRect& crect = this->centerRect();
	painter.setClip(crect);

	if (member.rect.valid())
	{
		painter.setColor(textColorRole);
		member.paint(painter);
	}

	if (member.current && member.iconRect.valid())
	{
		BImage* image = member.current->image();
		if (BIcon* icon = member.current->icon())
		{
			image = icon->image();
		}
		if (image)
		{
			painter.setColor(member.current->color());
			painter.drawImage(member.iconRect, image, true);
		}
		else if (member.current->graph())
		{
			painter.setColor(member.current->color());
			painter.setLineSmooth(true);
			painter.drawGraph(member.iconRect % 80, member.current->graph());
		}
	}

	if (!member.readOnly)
	{
		if (member.inputShown && member.inputCount == 0)
		{
			painter.setColor(Color_Indicator);
			painter.drawLine(member.lineBegin, member.lineEnd);
		}
	}
}

void BComboBox::activeEvent(const BEvent& event)
{
	if (this->actived())
	{

	}
	else
	{
		member.board->arrow->setChecked(false);
	}
	BWidget::activeEvent(event);
}
void BComboBox::focusEvent(const BEvent& event)
{
	if (!member.readOnly)
	{
		if (this->focused())
		{
			member.inputShown = true;
			member.inputIndex = 0;
			member.inputCount = member.cells.size();
			this->fresh();
		}
		else
		{
			member.inputShown = false;
			if (member.editing)
			{
				member.editing = false;
				emit(Signal_EditFinished, member.text);
				emit(Signal_TextChanged, member.text);
			}
			this->fresh();
		}
	}
	BWidget::focusEvent(event);
}

void BComboBox::keyDownEvent(const BKeyEvent& keyEvent)
{
	Key key = keyEvent.key();
	if (keyEvent.keyState(Key_LCtrl))
	{
		if (key == Key_V)
		{
			if (member.inputIndex > -1 && member.inputCount > 0)
			{
				member.remove(member.inputIndex, member.inputCount);
			}
			BString text;
			bool ret = BSystem::GetClipBoardText(text);
			text.replace('\n', ' ');
			member.input(text);
			member.dirty = true;
			this->fresh();
		}
		else if (key == Key_C)
		{
			BString text;
			if (member.inputIndex > -1)
			{
				if (member.inputCount > 0)
					text = member.substr(member.inputIndex, member.inputCount);
				else
					text = member.text;
			}
			bool ret = BSystem::SetClipBoardText(text);
		}
	}
	else if (key == Key_Enter)
	{
		this->setFocused(false);
	}
	else if (key == Key_Left)
	{
		if (member.inputCount > 0)
		{
			member.inputCount = 0;
			this->fresh();
		}
		else if (member.inputIndex > 0)
		{
			member.inputIndex--;
			this->fresh();
		}
		member.blinkTick = clock() + 500;
	}
	else if (key == Key_Right)
	{
		if (member.inputCount > 0)
		{
			member.inputIndex += member.inputCount;
			member.inputCount = 0;
			this->fresh();
		}
		else if (member.inputIndex < member.cells.size())
		{
			member.inputIndex++;
			this->fresh();
		}
		member.blinkTick = clock() + 500;
	}
	else if (key == Key_Back)
	{
		if (member.inputCount > 0)
		{
			if (member.remove(member.inputIndex, member.inputCount))
			{
				member.inputCount = 0;
				member.editing = true;
				member.dirty = true;
				emit(Signal_TextEdited, member.text);
				emit(Signal_TextChanged, member.text);
				this->fresh();
			}
		}
		else
		{
			member.inputIndex--;
			if (member.remove(member.inputIndex))
			{
				member.editing = true;
				member.dirty = true;
				emit(Signal_TextEdited, member.text);
				emit(Signal_TextChanged, member.text);
				this->fresh();
			}
		}
		member.blinkTick = clock() + 500;
	}
	else if (key == Key_Delete)
	{
		if (member.inputCount > 0)
		{
			if (member.remove(member.inputIndex, member.inputCount))
			{
				member.inputCount = 0;
				member.editing = true;
				member.dirty = true;
				emit(Signal_TextEdited, member.text);
				emit(Signal_TextChanged, member.text);
				this->fresh();
			}
		}
		else
		{
			if (member.remove(member.inputIndex))
			{
				member.editing = true;
				member.dirty = true;
				emit(Signal_TextEdited, member.text);
				emit(Signal_TextChanged, member.text);
				this->fresh();
			}
		}
		member.blinkTick = clock() + 500;
	}
	BWidget::keyDownEvent(keyEvent);
}
void BComboBox::keyUpEvent(const BKeyEvent& keyEvent)
{
	BWidget::keyUpEvent(keyEvent);
}
void BComboBox::charEvent(const BCharEvent& charEvent)
{
	BCode code = charEvent.code();
	bool edited = member.removeSelection();

	if (member.input(code))
	{
		edited = true;
	}
	if (edited)
	{
		member.editing = true;
		member.dirty = true;
		emit(Signal_TextEdited, member.text);
		emit(Signal_TextChanged, member.text);
		this->fresh();
	}
	BWidget::charEvent(charEvent);
}

void BComboBox::mouseEnterEvent(const BMouseEvent& mouseEvent)
{
	if (!member.readOnly)
	{
		member.hoverIndex = -1;
		this->setCursor(Cursor_Arrow);
	}
	else
	{
		member.state = State_Hovered;
		member.arrowState = State_Normal;
		this->fresh();
	}
	BWidget::mouseEnterEvent(mouseEvent);
}
void BComboBox::mouseLeaveEvent(const BMouseEvent& mouseEvent)
{
	if (member.readOnly)
	{
		member.state = State_Normal;
		member.arrowState = State_Normal;
		this->fresh();
	}
	else
	{
		member.hoverIndex = -1;
		this->setCursor(Cursor_Arrow);
	}
	BWidget::mouseLeaveEvent(mouseEvent);
}

void BComboBox::mousePressEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		const BPoint& pos = mouseEvent.pos();
		int hoverIndex = member.hoverIndex;
		if (member.inputIndex != member.hoverIndex)
		{
			member.inputIndex = member.hoverIndex;
			member.inputCount = 0;
			this->fresh();
		}
		member.pressed = true;
		member.pressIndex = member.hoverIndex;
		member.blinkTick = clock() + 500;
	}
	BWidget::mousePressEvent(mouseEvent);
}
void BComboBox::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	member.pressed = false;
	member.pressIndex = -1;
	this->fresh();
	BWidget::mouseReleaseEvent(mouseEvent);
}
void BComboBox::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	const BPoint& pos = mouseEvent.pos();
	if (!member.readOnly)
	{
		if (this->centerRect().contain(pos))
			this->setCursor(Cursor_Ibeam);
		else
			this->setCursor(Cursor_Arrow);
		int index = member.indexAt(pos);
		if (member.hoverIndex != index)
		{
			member.hoverIndex = index;
		}
		if (member.pressed)
		{
			int inputIndex = bMin(member.pressIndex, member.hoverIndex);
			int inputCount = abs(member.pressIndex - member.hoverIndex);
			if (member.inputIndex != inputIndex || member.inputCount != inputCount)
			{
				member.inputIndex = inputIndex;
				member.inputCount = inputCount;
				this->fresh();
			}
		}
	}
	BWidget::mouseMoveEvent(mouseEvent);
}

void BComboBox::mouseClickEvent(const BMouseEvent& mouseEvent)
{
	const BPoint& pos = mouseEvent.pos();
	const BRect& crect = this->centerRect();
	if (crect.contain(pos) && member.readOnly)
	{
		if (mouseEvent.button() == Button_Left)
		{
			bool checked = member.board->arrow->checked();
			member.board->arrow->setChecked(!checked);
		}
	}
	BWidget::mouseClickEvent(mouseEvent);
}
void BComboBox::mouseDoubleClickEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		if (member.selectWord())
		{
			this->fresh();
		}
	}
	BWidget::mouseDoubleClickEvent(mouseEvent);
}

